package console;

import collector.Chain;
import collector.Collector;
import collector.Configuration;
import collector.FileObj;
import org.apache.commons.cli.*;

import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;

/**
 * This is the main entry point for the tool when invoked from the command line.  The --help option
 * will display information on the usage of the tool.
 */
public class App implements Logger {

    public static void main(String[] args) throws Exception {
        CommandLineParser cmdparser = new PosixParser();
        Configuration configuration = new Configuration();
        Options options = new Options();
        Option types = new Option("t", "types", false, "anonymize types");
        Option vars = new Option("v", "variables", false, "anonymize variable names");
        Option literals = new Option("l", "literals", false, "anonymize literals");
        Option dump = new Option("d", "dumpast", false, "dump ast representation");
        Option statements = new Option("s", "statements", true, "minumum number of statements in a block.  default " + configuration.minLengthForChunk);
        statements.setArgName("number");
        options.addOption(types);
        options.addOption(vars);
        options.addOption(literals);
        options.addOption(dump);
        options.addOption(statements);
        CommandLine line;
        String[] fileArgs;
        try {
            line = cmdparser.parse(options, args);
            fileArgs = line.getArgs();
        } catch (ParseException e) {
            HelpFormatter helpFormatter = new HelpFormatter();
            helpFormatter.printHelp("App ", options, true);
            System.exit(1);
            throw new RuntimeException(); // allows fileArgs to be final
        }
        configuration.setAnonymizeTypes(line.hasOption(types.getOpt()));
        configuration.setAnonymizeVariables(line.hasOption(vars.getOpt()));
        configuration.setAnonymizeLiterals(line.hasOption(literals.getOpt()));
        configuration.setDumpAst(line.hasOption(dump.getOpt()));
        if (line.hasOption(statements.getOpt())) {
            configuration.minLengthForChunk = Integer.parseInt(line.getOptionValue(statements.getOpt()));
        }
        Collector.setDebug(Collector.DebugReport.MEMORY, true);
        App app = new App(configuration, fileArgs);
        configuration.setLogger(app);
        System.exit(app.run());
    }

    private final String[] fileArgs;
    private final Configuration c;

    public App(Configuration configuration, String[] args) {
        c = configuration;
        fileArgs = args;
    }

    public int run() throws Exception {
        ArrayList<FileObj> files = new ArrayList<FileObj>();
        for (String arg : fileArgs) {
            addJavaFiles(new File(arg), files);
        }
        Collector collector = new Collector(null, files, c);
        collector.findDuplicatedCode();

        // dump intermediate representation
        if (c.isDumpAst()) {
            c.info(files.get(0).getStatements());
            System.exit(0);
        }

        int nods = 0;
        for (FileObj f : files) {
            nods += f.getNumberOfWarningStatement();
        }
        int nos = collector.getProjectNumOfStatement();
        DecimalFormat df = new DecimalFormat("###.##");
        String pdc = df.format((100.0 * nods) / nos);

        String types = c.getAnonymized();

        if (types.length() > 0) {
            c.info("Fuzzy match with equate set: " + types);
        }
        // This would be a good target for StringTemplate
        c.info("The percentage of duplicated codes:" + pdc);
        c.info("The number of scanned files:" + files.size());
        c.info("The number of containing-duplication-chunk files:" + getOnlyFilesHasDup(files).size());
        c.info("The number of physical lines:" + collector.getNumberOfPhysicalLines());
        c.info("The number of statements:" + nos);
        c.info("The number of duplicated statements:" + nods);
        c.info("(One statement can be counted more than one time when it belongs to more than one chunk)");
        c.info("The minimum statements in one chunk:" + c.minLengthForChunk);

        ArrayList<Chain> chains = collector.getChains();
        if (chains.size() > 0) {
            Collections.sort(chains, Chain.NumOfStateDecrease);
            c.error("Duplicate chains:");
            for (Chain chain : chains) {
                c.error(chain.toReport());
            }
//            c.info("Longest chain: " + chains.get(0).toReport());
//            Collections.sort(chains, Chain.NumOfChunkDecrease);
//            c.info("Most chunks: " + chains.get(0).toReport());
        } else {
            c.info("No duplicate code found!");
        }
        return chains.size();
    }

    /*
    * Recursion explore the folder to get the java source code
    */
    private static void addJavaFiles(File f, ArrayList<FileObj> files) {
        //If this is a directory, walk each file/dir in that directory
        if (f.isDirectory()) {
            File[] list = f.listFiles();
            for (File file : list) {
                addJavaFiles(file, files);
            }
        }
        // otherwise, if this is a java file, parse it!
        else if (f.getName().endsWith(".java")) {
            files.add(new FileObj(f));
        }
    }

    public static ArrayList<FileObj> getOnlyFilesHasDup(ArrayList<FileObj> files) {
        ArrayList<FileObj> containDupFiles = new ArrayList<FileObj>();
        for (FileObj file : files) {
            if (file.getChunks().size() > 0) {
                containDupFiles.add(file);
            }
        }
        return containDupFiles;
    }

    public void info(String s) {
        System.out.println(s);
    }

    public void error(String s) {
        System.err.println(s);
    }
}
